/*
* bubbletip
*
* Copyright (c) 2009-2010, UhLeeKa.
* Version:
*      1.1.0
* Licensed under the GNU Lesser General Public License:
*     http://www.gnu.org/licenses/lgpl-3.0.html
* Author Website:
*     http://www.uhleeka.com
* Project Hosting on Google Code:
*     http://code.google.com/p/bubbletip/
*/
; (function($) {
        var bindIndex = 0;
        $.fn.extend({
                bubbletip: function(tip, options) {
                        // check to see if the tip is a descendant of
                        // a table.bubbletip element and therefore
                        // has already been instantiated as a bubbletip
                        if (($(tip).length == 0) || ($(tip).eq(0).closest('table.bubbletip').length > 0)) {
                                return this;
                        }

                        var _this, _tip, _options, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex;
                        // hack for IE6,IE7
                        var _windowWidth, _windowHeight;

                        _this = $(this);
                        _tip = $(tip).eq(0);
                        _bindIndex = bindIndex++;  // for window.resize namespace binding
                        _options = {
                                positionAt: 'element', // element | body | mouse
                                positionAtElement: _this,
                                offsetTop: 0,
                                offsetLeft: 0,
                                deltaPosition: 30,
                                deltaDirection: 'up', // direction: up | down | left | right
                                animationDuration: 250,
                                animationEasing: 'swing', // linear | swing
                                bindShow: 'mouseover', // mouseover | focus | click | etc.
                                bindHide: 'mouseout', // mouseout | blur | etc.
                                delayShow: 0,
                                delayHide: 500,
                                calculateOnShow: false
                        };
                        if (options) {
                                _options = $.extend(_options, options);
                        }
                        // calculated values
                        _calc = {
                                top: 0,
                                left: 0,
                                delta: 0,
                                mouseTop: 0,
                                mouseLeft: 0,
                                tipHeight: 0,
                                bindShow: (_options.bindShow + ' ').replace(/ +/g, '.bubbletip' + _bindIndex + ' '),
                                bindHide: (_options.bindHide + ' ').replace(/ +/g, '.bubbletip' + _bindIndex + ' ')
                        };
                        _timeoutAnimate = null;
                        _timeoutRefresh = null;
                        _isActive = false;
                        _isHiding = false;

                        // store the tip id for removeBubbletip
                        if (!_this.data('bubbletip_tips')) {
                                _this.data('bubbletip_tips', [[_tip.get(0), _bindIndex]]);
                        } else {
                                _this.data('bubbletip_tips', $.merge(_this.data('bubbletip_tips'), [[_tip.get(0), _bindIndex]]));
                        }


                        // validate _options
                        if (!_options.positionAt.match(/^element|body|mouse$/i)) {
                                _options.positionAt = 'element';
                        }
                        if (!_options.deltaDirection.match(/^up|down|left|right$/i)) {
                                _options.deltaDirection = 'up';
                        }

                        // create the wrapper table element
                        if (_options.deltaDirection.match(/^up$/i)) {
                                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td><table class="bt-bottom" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-bottomright"></td></tr></tbody></table>');
                        } else if (_options.deltaDirection.match(/^down$/i)) {
                                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td><table class="bt-top" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
                        } else if (_options.deltaDirection.match(/^left$/i)) {
                                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right-tail"><div class="bt-right"></div><div class="bt-right-tail"></div><div class="bt-right"></div></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
                        } else if (_options.deltaDirection.match(/^right$/i)) {
                                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left-tail"><div class="bt-left"></div><div class="bt-left-tail"></div><div class="bt-left"></div></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
                        }

                        // append the wrapper to the document body
                        _wrapper.appendTo('body');

                        // apply IE filters to _wrapper elements
                        if ((/msie/.test(navigator.userAgent.toLowerCase())) && (!/opera/.test(navigator.userAgent.toLowerCase()))) {
                                $('*', _wrapper).each(function() {
                                        var image = $(this).css('background-image');
                                        if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
                                                image = RegExp.$1;
                                                $(this).css({
                                                        'backgroundImage': 'none',
                                                        'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=' + ($(this).css('backgroundRepeat') == 'no-repeat' ? 'crop' : 'scale') + ', src=\'' + image + '\')'
                                                }).each(function() {
                                                        var position = $(this).css('position');
                                                        if (position != 'absolute' && position != 'relative')
                                                                $(this).css('position', 'relative');
                                                });
                                        }
                                });
                        }

                        // move the tip element into the content section of the wrapper
                        $('.bt-content', _wrapper).append(_tip);
                        // show the tip (in case it is hidden) so that we can calculate its dimensions
                        _tip.show();
                        // handle left|right delta
                        if (_options.deltaDirection.match(/^left|right$/i)) {
                                // tail is 40px, so divide height by two and subtract 20px;
                                _calc.tipHeight = parseInt(_tip.height() / 2);
                                // handle odd integer height
                                if ((_tip.height() % 2) == 1) {
                                        _calc.tipHeight++;
                                }
                                _calc.tipHeight = (_calc.tipHeight < 20) ? 1 : _calc.tipHeight - 20;
                                if (_options.deltaDirection.match(/^left$/i)) {
                                        $('div.bt-right', _wrapper).css('height', _calc.tipHeight + 'px');
                                } else {
                                        $('div.bt-left', _wrapper).css('height', _calc.tipHeight + 'px');
                                }
                        }
                        // set the opacity of the wrapper to 0
                        _wrapper.css('opacity', 0);
                        // hack for FF 3.6
                        _wrapper.css({ 'width': _wrapper.width(), 'height': _wrapper.height() });
                        // execute initial calculations
                        _Calculate();
                        _wrapper.hide();

                        // handle window.resize
                        $(window).bind('resize.bubbletip' + _bindIndex, function() {
                                var w = $(window).width();
                                var h = $(window).height();

                                if ((w === _windowWidth) && (h === _windowHeight)) {
                                        return;
                                }
                                _windowWidth = w;
                                _windowHeight = h;

                                if (_timeoutRefresh) {
                                        clearTimeout(_timeoutRefresh);
                                }
                                _timeoutRefresh = setTimeout(function() {
                                        _Calculate();
                                }, 250);
                        });

                        // handle mouseover and mouseout events
                        $([_wrapper.get(0), this.get(0)]).bind(_calc.bindShow, function() {
                                if (_timeoutAnimate) {
                                        clearTimeout(_timeoutAnimate);
                                }
                                if (_options.delayShow === 0) {
                                        _Show();
                                } else {
                                        _timeoutAnimate = setTimeout(function() {
                                                _Show();
                                        }, _options.delayShow);
                                }
                                return false;
                        }).bind(_calc.bindHide, function() {
                                if (_timeoutAnimate) {
                                        clearTimeout(_timeoutAnimate);
                                }
                                if (_options.delayHide === 0) {
                                        _Hide();
                                } else {
                                        _timeoutAnimate = setTimeout(function() {
                                                _Hide();
                                        }, _options.delayHide);
                                }
                                return false;
                        });

                        function _Show() {
                                var animation;

                                if (_isActive) { // the tip is currently showing; do nothing
                                        return;
                                }
                                _isActive = true;
                                if (_isHiding) { // the tip is currently hiding; interrupt and start showing again
                                        _wrapper.stop(true, false);
                                }

                                if (_options.calculateOnShow) {
                                        _Calculate();
                                }
                                if (_options.positionAt.match(/^element|body$/i)) {
                                        if (_options.deltaDirection.match(/^up|down$/i)) {
                                                if (!_isHiding) {
                                                        _wrapper.css('top', parseInt(_calc.top + _calc.delta) + 'px');
                                                }
                                                animation = { 'top': _calc.top + 'px' };
                                        } else {
                                                if (!_isHiding) {
                                                        _wrapper.css('left', parseInt(_calc.left + _calc.delta) + 'px');
                                                }
                                                animation = { 'left': _calc.left + 'px' };
                                        }
                                } else {
                                        if (_options.deltaDirection.match(/^up|down$/i)) {
                                                if (!_isHiding) {
                                                        _calc.mouseTop = e.pageY + _calc.top;
                                                        _wrapper.css({ 'top': parseInt(_calc.mouseTop + _calc.delta) + 'px', 'left': parseInt(e.pageX - (_wrapper.width() / 2)) + 'px' });
                                                }
                                                animation = { 'top': _calc.mouseTop + 'px' };
                                        } else {
                                                if (!_isHiding) {
                                                        _calc.mouseLeft = e.pageX + _calc.left;
                                                        _wrapper.css({ 'left': parseInt(_calc.mouseLeft + _calc.delta) + 'px', 'top': parseInt(e.pageY - (_wrapper.height() / 2)) + 'px' });
                                                }
                                                animation = { 'left': _calc.left + 'px' };
                                        }
                                }
                                _isHiding = false;
                                _wrapper.show();
                                animation = $.extend(animation, { 'opacity': 1 });
                                _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                                        _wrapper.css('opacity', '');
                                        _isActive = true;
                                });
                        };
                        function _Hide() {
                                var animation;

                                _isActive = false;
                                _isHiding = true;
                                if (_options.positionAt.match(/^element|body$/i)) {
                                        if (_options.deltaDirection.match(/^up|down$/i)) {
                                                animation = { 'top': parseInt(_calc.top - _calc.delta) + 'px' };
                                        } else {
                                                animation = { 'left': parseInt(_calc.left - _calc.delta) + 'px' };
                                        }
                                } else {
                                        if (_options.deltaDirection.match(/^up|down$/i)) {
                                                animation = { 'top': parseInt(_calc.mouseTop - _calc.delta) + 'px' };
                                        } else {
                                                animation = { 'left': parseInt(_calc.mouseLeft - _calc.delta) + 'px' };
                                        }
                                }
                                animation = $.extend(animation, { 'opacity': 0 });
                                _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                                        _wrapper.hide();
                                        _isHiding = false;
                                });
                        };
                        function _Calculate() {
                                // calculate values
                                if (_options.positionAt.match(/^element$/i)) {
                                        var offset = _options.positionAtElement.offset();
                                        if (_options.deltaDirection.match(/^up$/i)) {
                                                _calc.top = offset.top + _options.offsetTop - _wrapper.outerHeight();
                                                _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
                                                _calc.delta = _options.deltaPosition;
                                        } else if (_options.deltaDirection.match(/^down$/i)) {
                                                _calc.top = offset.top + _options.positionAtElement.outerHeight() + _options.offsetTop;
                                                _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
                                                _calc.delta = -_options.deltaPosition;
                                        } else if (_options.deltaDirection.match(/^left$/i)) {
                                                _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
                                                _calc.left = offset.left + _options.offsetLeft - _wrapper.outerWidth();
                                                _calc.delta = _options.deltaPosition;
                                        } else if (_options.deltaDirection.match(/^right$/i)) {
                                                _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
                                                _calc.left = offset.left + _options.positionAtElement.outerWidth() + _options.offsetLeft;
                                                _calc.delta = -_options.deltaPosition;
                                        }
                                } else if (_options.positionAt.match(/^body$/i)) {
                                        if (_options.deltaDirection.match(/^up|left$/i)) {
                                                _calc.top = _options.offsetTop;
                                                _calc.left = _options.offsetLeft;
                                                // up or left
                                                _calc.delta = _options.deltaPosition;
                                        } else {
                                                if (_options.deltaDirection.match(/^down$/i)) {
                                                        _calc.top = parseInt(_options.offsetTop + _wrapper.outerHeight());
                                                        _calc.left = _options.offsetLeft;
                                                } else {
                                                        _calc.top = _options.offsetTop;
                                                        _calc.left = parseInt(_options.offsetLeft + _wrapper.outerWidth());
                                                }
                                                // down or right
                                                _calc.delta = -_options.deltaPosition;
                                        }
                                } else if (_options.positionAt.match(/^mouse$/i)) {
                                        if (_options.deltaDirection.match(/^up|left$/i)) {
                                                if (_options.deltaDirection.match(/^up$/i)) {
                                                        _calc.top = -(_options.offsetTop + _wrapper.outerHeight());
                                                        _calc.left = _options.offsetLeft;
                                                } else if (_options.deltaDirection.match(/^left$/i)) {
                                                        _calc.top = _options.offsetTop;
                                                        _calc.left = -(_options.offsetLeft + _wrapper.outerWidth());
                                                }
                                                // up or left
                                                _calc.delta = _options.deltaPosition;
                                        } else {
                                                _calc.top = _options.offsetTop;
                                                _calc.left = _options.offsetLeft;
                                                // down or right
                                                _calc.delta = -_options.deltaPosition;
                                        }
                                }
                                // handle the wrapper (element|body) positioning
                                if (_options.positionAt.match(/^element|body$/i)) {
                                        _wrapper.css({
                                                'position': 'absolute',
                                                'top': _calc.top + 'px',
                                                'left': _calc.left + 'px'
                                        });
                                }
                        };
                        return this;
                },
                removeBubbletip: function(tips) {
                        var tipsActive;
                        var tipsToRemove = new Array();
                        var tipsActiveAdjusted = new Array();
                        var arr, i, ix;
                        var elem;

                        tipsActive = $.makeArray($(this).data('bubbletip_tips'));

                        // convert the parameter array of tip id's or elements to id's
                        arr = $.makeArray(tips);
                        for (i = 0; i < arr.length; i++) {
                                tipsToRemove.push($(arr[i]).get(0));
                        }

                        for (i = 0; i < tipsActive.length; i++) {
                                ix = null;
                                if ((tipsToRemove.length == 0) || ((ix = $.inArray(tipsActive[i][0], tipsToRemove)) >= 0)) {
                                        // remove all tips if there are none specified
                                        // otherwise, remove only specified tips

                                        // find the surrounding table.bubbletip
                                        elem = $(tipsActive[i][0]).closest('table.bubbletip');
                                        // attach the tip element to body and hide
                                        $(tipsActive[i][0]).appendTo('body').hide();
                                        // remove the surrounding table.bubbletip
                                        $(elem).remove();

                                        // unbind show/hide events
                                        $(this).unbind('.bubbletip' + tipsActive[i][1]);

                                        // unbind window.resize event
                                        $(window).unbind('.bubbletip' + tipsActive[i][1]);
                                } else {
                                        // tip is not being removed, so add it to the adjusted array
                                        tipsActiveAdjusted.push(tipsActive[i]);
                                }
                        }
                        $(this).data('bubbletip_tips', tipsActiveAdjusted);

                        return this;
                }
        });
})(jQuery);
